#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS 1
#endif

#include <stdio.h>

#include <errno.h>

#ifndef windows_x86
#include <netdb.h>
#include <ifaddrs.h>
#else
#include <winsock2.h>
#include <iphlpapi.h>
#define sleep(x) Sleep(x * 1000)
#endif
#ifdef LINUX
//#include <termio.h> /* POSIX terminal control definitions */
#include <sys/socket.h>
#include <netinet/in.h>  /* required for htons() and ntohs() */
#include <netinet/tcp.h> /* TCP level socket options */
#include <netinet/ip.h>  /* IP  level socket options */
#include <pthread.h>
#include <sched.h> /* sched_yield() */
#endif

// Alias some things to simulate recieving data to fuzz library
#if defined(MDNS_FUZZING)
#define recvfrom(sock, buffer, capacity, flags, src_addr, addrlen) ((mdns_ssize_t)capacity)
#define printf
#endif

#include "mdns.h"

#if defined(MDNS_FUZZING)
#undef recvfrom
#endif

static char addrbuffer[64];
static char entrybuffer[256];
static char namebuffer[256];
static char sendbuffer[1024];
static mdns_record_txt_t txtbuffer[128];

static struct sockaddr_in service_address_ipv4;
static struct sockaddr_in6 service_address_ipv6;

static int has_ipv4;
static int has_ipv6;

// Data for our service including the mDNS records
typedef struct {
    mdns_string_t service;
    mdns_string_t hostname;
    mdns_string_t service_instance;
    mdns_string_t hostname_qualified;
    struct sockaddr_in address_ipv4;
    struct sockaddr_in6 address_ipv6;
    u16 port;
    mdns_record_t record_ptr;
    mdns_record_t record_srv;
    mdns_record_t record_a;
    mdns_record_t record_aaaa;
    mdns_record_t txt_record[3];
} service_t;

static mdns_string_t
ipv4_address_to_string(char *buffer, size_t capacity, const struct sockaddr_in *addr,
                       size_t addrlen) {
    char host[NI_MAXHOST] = {0};
    char service[NI_MAXSERV] = {0};
    int ret = getnameinfo((const struct sockaddr *) addr, addrlen, host, NI_MAXHOST, service,
                          NI_MAXSERV, NI_NUMERICSERV | NI_NUMERICHOST);
    int len = 0;
    if (ret == 0) {
        if (addr->sin_port != 0)
            len = snprintf(buffer, capacity, "%s:%s", host, service);
        else
            len = snprintf(buffer, capacity, "%s", host);
    }
    if (len >= (int) capacity)
        len = (int) capacity - 1;
    mdns_string_t str;
    str.str = buffer;
    str.length = len;
    return str;
}

static mdns_string_t
ipv6_address_to_string(char *buffer, size_t capacity, const struct sockaddr_in6 *addr,
                       size_t addrlen) {
    char host[NI_MAXHOST] = {0};
    char service[NI_MAXSERV] = {0};
    int ret = getnameinfo((const struct sockaddr *) addr, addrlen, host, NI_MAXHOST, service,
                          NI_MAXSERV, NI_NUMERICSERV | NI_NUMERICHOST);
    int len = 0;
    if (ret == 0) {
        if (addr->sin6_port != 0)
            len = snprintf(buffer, capacity, "[%s]:%s", host, service);
        else
            len = snprintf(buffer, capacity, "%s", host);
    }
    if (len >= (int) capacity)
        len = (int) capacity - 1;
    mdns_string_t str;
    str.str = buffer;
    str.length = len;
    return str;
}

static mdns_string_t
ip_address_to_string(char *buffer, size_t capacity, const struct sockaddr *addr, size_t addrlen) {
    if (addr->sa_family == AF_INET6)
        return ipv6_address_to_string(buffer, capacity, (const struct sockaddr_in6 *) addr, addrlen);
    return ipv4_address_to_string(buffer, capacity, (const struct sockaddr_in *) addr, addrlen);
}

// Callback handling parsing answers to queries sent
static int
query_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_entry_type_t entry,
               uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void *data,
               size_t size, size_t name_offset, size_t name_length, size_t record_offset,
               size_t record_length, const void *user_data) {
    (void) sizeof(sock);
    (void) sizeof(query_id);
    (void) sizeof(name_length);
    (void) sizeof(user_data);
    mdns_string_t fromaddrstr = ip_address_to_string(addrbuffer, sizeof(addrbuffer), from, addrlen);
    const char *entrytype;
    if (entry == MDNS_ENTRYTYPE_ANSWER) {
        entrytype = "answer";
    } else {
        entrytype = (entry == MDNS_ENTRYTYPE_AUTHORITY) ? "authority" : "additional";
    }
    mdns_string_t entrystr =
            mdns_string_extract(data, size, &name_offset, entrybuffer, sizeof(entrybuffer));
    if (rtype == MDNS_RECORDTYPE_PTR) {
        mdns_string_t namestr = mdns_record_parse_ptr(data, size, record_offset, record_length,
                                                      namebuffer, sizeof(namebuffer));
        //printf("%.*s : %s %.*s PTR %.*s rclass 0x%x ttl %u length %d\n",
            //    MDNS_STRING_FORMAT(fromaddrstr), entrytype, MDNS_STRING_FORMAT(entrystr),
            //    MDNS_STRING_FORMAT(namestr), rclass, ttl, (int) record_length);
    } else if (rtype == MDNS_RECORDTYPE_SRV) {
        mdns_record_srv_t srv = mdns_record_parse_srv(data, size, record_offset, record_length,
                                                      namebuffer, sizeof(namebuffer));
        //printf("%.*s : %s %.*s SRV %.*s priority %d weight %d port %d\n",
            //    MDNS_STRING_FORMAT(fromaddrstr), entrytype, MDNS_STRING_FORMAT(entrystr),
            //    MDNS_STRING_FORMAT(srv.name), srv.priority, srv.weight, srv.port);
    } else if (rtype == MDNS_RECORDTYPE_A) {
        struct sockaddr_in addr;
        mdns_record_parse_a(data, size, record_offset, record_length, &addr);
        mdns_string_t addrstr =
                ipv4_address_to_string(namebuffer, sizeof(namebuffer), &addr, sizeof(addr));
        //printf("%.*s : %s %.*s A %.*s\n", MDNS_STRING_FORMAT(fromaddrstr), entrytype,
            //    MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(addrstr));
    } else if (rtype == MDNS_RECORDTYPE_AAAA) {
        struct sockaddr_in6 addr;
        mdns_record_parse_aaaa(data, size, record_offset, record_length, &addr);
        mdns_string_t addrstr =
                ipv6_address_to_string(namebuffer, sizeof(namebuffer), &addr, sizeof(addr));
        //printf("%.*s : %s %.*s AAAA %.*s\n", MDNS_STRING_FORMAT(fromaddrstr), entrytype,
            //    MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(addrstr));
    } else if (rtype == MDNS_RECORDTYPE_TXT) {
        size_t parsed = mdns_record_parse_txt(data, size, record_offset, record_length, txtbuffer,
                                              sizeof(txtbuffer) / sizeof(mdns_record_txt_t));
        for (size_t itxt = 0; itxt < parsed; ++itxt) {
            if (txtbuffer[itxt].value.length) {
                //printf("%.*s : %s %.*s TXT %.*s = %.*s\n", MDNS_STRING_FORMAT(fromaddrstr),
                    //    entrytype, MDNS_STRING_FORMAT(entrystr),
                    //    MDNS_STRING_FORMAT(txtbuffer[itxt].key),
                    //    MDNS_STRING_FORMAT(txtbuffer[itxt].value));
            } else {
                //printf("%.*s : %s %.*s TXT %.*s\n", MDNS_STRING_FORMAT(fromaddrstr), entrytype,
                    //    MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(txtbuffer[itxt].key));
            }
        }
    } else {
        //printf("%.*s : %s %.*s type %u rclass 0x%x ttl %u length %d\n",
            //    MDNS_STRING_FORMAT(fromaddrstr), entrytype, MDNS_STRING_FORMAT(entrystr), rtype,
            //    rclass, ttl, (int) record_length);
    }
    return 0;
}

// Callback handling questions incoming on service sockets
static int
service_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_entry_type_t entry,
                 uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void *data,
                 size_t size, size_t name_offset, size_t name_length, size_t record_offset,
                 size_t record_length, const void *user_data) {
    (void) sizeof(ttl);
    if (entry != MDNS_ENTRYTYPE_QUESTION)
        return 0;

    const char dns_sd[] = "_pac._udp.local.";
    const service_t *service = (const service_t *) user_data;

    ip_address_to_string(addrbuffer, sizeof(addrbuffer), from, addrlen);

    size_t offset = name_offset;
    mdns_string_t name = mdns_string_extract(data, size, &offset, namebuffer, sizeof(namebuffer));

    const char *record_name = 0;
    if (rtype == MDNS_RECORDTYPE_PTR)
        record_name = "PTR";
    else if (rtype == MDNS_RECORDTYPE_SRV)
        record_name = "SRV";
    else if (rtype == MDNS_RECORDTYPE_A)
        record_name = "A";
    else if (rtype == MDNS_RECORDTYPE_AAAA)
        record_name = "AAAA";
    else if (rtype == MDNS_RECORDTYPE_ANY)
        record_name = "ANY";
    else
        return 0;
    //printf("Query %s %.*s\n", record_name, MDNS_STRING_FORMAT(name));
    int this = 0;
    if ((name.length == (sizeof(dns_sd) - 1)) &&
        (strncmp(name.str, dns_sd, sizeof(dns_sd) - 1) == 0)) {
        //printf("match:%s\n", name.str);
        this = 1;
    } else if ((name.length == service->service.length) &&
               (strncmp(name.str, service->service.str, name.length) == 0)) {
        //printf("match:%s\n", name.str);
        this = 1;
    } else if ((name.length == service->service_instance.length) &&
               (strncmp(name.str, service->service_instance.str, name.length) == 0)) {
        //printf("match:%s\n", name.str);
        this = 1;
    } else if ((name.length == service->hostname_qualified.length) &&
               (strncmp(name.str, service->hostname_qualified.str, name.length) == 0)) {
        //printf("match:%s\n", name.str);
        this = 1;
    }
    if (this && ((rtype == MDNS_RECORDTYPE_PTR) || (rtype == MDNS_RECORDTYPE_ANY))) {
        // The PTR query was for the DNS-SD domain, send answer with a PTR record for the
        // service name we advertise, typically on the "<_service-name>._tcp.local." format

        // Answer PTR record reverse mapping "<_service-name>._tcp.local." to
        // "<hostname>.<_service-name>._tcp.local."
        mdns_record_t answer = {
                .name = name, .type = MDNS_RECORDTYPE_PTR, .data.ptr.name = service->service};

        // Send the answer, unicast or multicast depending on flag in query
        uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
        //printf("  --> answer %.*s (%s)\n", MDNS_STRING_FORMAT(answer.data.ptr.name),
            //    (unicast ? "unicast" : "multicast"));

        if (unicast) {
            mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
                                      rtype, name.str, name.length, &answer, 0, 0, 0, 0);
        } else {
            mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), &answer, 0, 0, 0, 0);
        }
    }
    if (this && ((rtype == MDNS_RECORDTYPE_PTR) || (rtype == MDNS_RECORDTYPE_ANY))) {
        // The PTR query was for our service (usually "<_service-name._tcp.local"), answer a PTR
        // record reverse mapping the queried service name to our service instance name
        // (typically on the "<hostname>.<_service-name>._tcp.local." format), and add
        // additional records containing the SRV record mapping the service instance name to our
        // qualified hostname (typically "<hostname>.local.") and port, as well as any IPv4/IPv6
        // address for the hostname as A/AAAA records, and two test TXT records

        // Answer PTR record reverse mapping "<_service-name>._tcp.local." to
        // "<hostname>.<_service-name>._tcp.local."
        mdns_record_t answer = service->record_ptr;

        mdns_record_t additional[5] = {0};
        size_t additional_count = 0;

        // SRV record mapping "<hostname>.<_service-name>._tcp.local." to
        // "<hostname>.local." with port. Set weight & priority to 0.
        additional[additional_count++] = service->record_srv;

        // A/AAAA records mapping "<hostname>.local." to IPv4/IPv6 addresses
        if (service->address_ipv4.sin_family == AF_INET)
            additional[additional_count++] = service->record_a;
        if (service->address_ipv6.sin6_family == AF_INET6)
            additional[additional_count++] = service->record_aaaa;

        // Add two test TXT records for our service instance name, will be coalesced into
        // one record with both key-value pair strings by the library
        additional[additional_count++] = service->txt_record[0];
        additional[additional_count++] = service->txt_record[1];
        additional[additional_count++] = service->txt_record[2];

        // Send the answer, unicast or multicast depending on flag in query
        uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
        //printf("  --> answer %.*s (%s)\n", MDNS_STRING_FORMAT(service->record_ptr.data.ptr.name),
            //    (unicast ? "unicast" : "multicast"));

        if (unicast) {
            mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
                                      rtype, name.str, name.length, &answer, 0, 0, additional,
                                      additional_count);
        } else {
            mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), &answer, 0, 0,
                                        additional, additional_count);
        }
    }
    if (this && ((rtype == MDNS_RECORDTYPE_SRV) || (rtype == MDNS_RECORDTYPE_ANY))) {
        // The SRV query was for our service instance (usually
        // "<hostname>.<_service-name._tcp.local"), answer a SRV record mapping the service
        // instance name to our qualified hostname (typically "<hostname>.local.") and port, as
        // well as any IPv4/IPv6 address for the hostname as A/AAAA records, and two test TXT
        // records

        // Answer PTR record reverse mapping "<_service-name>._tcp.local." to
        // "<hostname>.<_service-name>._tcp.local."
        mdns_record_t answer = service->record_srv;

        mdns_record_t additional[5] = {0};
        size_t additional_count = 0;

        // A/AAAA records mapping "<hostname>.local." to IPv4/IPv6 addresses
        if (service->address_ipv4.sin_family == AF_INET)
            additional[additional_count++] = service->record_a;
        if (service->address_ipv6.sin6_family == AF_INET6)
            additional[additional_count++] = service->record_aaaa;

        // Add two test TXT records for our service instance name, will be coalesced into
        // one record with both key-value pair strings by the library
        additional[additional_count++] = service->txt_record[0];
        additional[additional_count++] = service->txt_record[1];
        additional[additional_count++] = service->txt_record[2];

        // Send the answer, unicast or multicast depending on flag in query
        uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
        //printf("  --> answer %.*s port %d (%s)\n",
            //    MDNS_STRING_FORMAT(service->record_srv.data.srv.name), service->port,
            //    (unicast ? "unicast" : "multicast"));

        if (unicast) {
            mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
                                      rtype, name.str, name.length, &answer, 0, 0, additional,
                                      additional_count);
        } else {
            mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), &answer, 0, 0,
                                        additional, additional_count);
        }
    }
    if (this && ((rtype == MDNS_RECORDTYPE_A) || (rtype == MDNS_RECORDTYPE_ANY)) &&
                (service->address_ipv4.sin_family == AF_INET)) {
        // The A query was for our qualified hostname (typically "<hostname>.local.") and we
        // have an IPv4 address, answer with an A record mappiing the hostname to an IPv4
        // address, as well as any IPv6 address for the hostname, and two test TXT records

        // Answer A records mapping "<hostname>.local." to IPv4 address
        mdns_record_t answer = service->record_a;

        mdns_record_t additional[5] = {0};
        size_t additional_count = 0;

        // AAAA record mapping "<hostname>.local." to IPv6 addresses
        if (service->address_ipv6.sin6_family == AF_INET6)
            additional[additional_count++] = service->record_aaaa;

        // Add two test TXT records for our service instance name, will be coalesced into
        // one record with both key-value pair strings by the library
        additional[additional_count++] = service->txt_record[0];
        additional[additional_count++] = service->txt_record[1];
        additional[additional_count++] = service->txt_record[2];

        // Send the answer, unicast or multicast depending on flag in query
        uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
        mdns_string_t addrstr = ip_address_to_string(
                addrbuffer, sizeof(addrbuffer), (const struct sockaddr *) &service->record_a.data.a.addr,
                sizeof(service->record_a.data.a.addr));
        //printf("  --> answer %.*s IPv4 %.*s (%s)\n", MDNS_STRING_FORMAT(service->record_a.name),
            //    MDNS_STRING_FORMAT(addrstr), (unicast ? "unicast" : "multicast"));

        if (unicast) {
            mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
                                      rtype, name.str, name.length, &answer, 0, 0, additional,
                                      additional_count);
        } else {
            mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), &answer, 0, 0,
                                        additional, additional_count);
        }
    } else if (this && ((rtype == MDNS_RECORDTYPE_AAAA) || (rtype == MDNS_RECORDTYPE_ANY)) &&
                       (service->address_ipv6.sin6_family == AF_INET6)) {
        // The AAAA query was for our qualified hostname (typically "<hostname>.local.") and we
        // have an IPv6 address, answer with an AAAA record mappiing the hostname to an IPv6
        // address, as well as any IPv4 address for the hostname, and two test TXT records

        // Answer AAAA records mapping "<hostname>.local." to IPv6 address
        mdns_record_t answer = service->record_aaaa;

        mdns_record_t additional[5] = {0};
        size_t additional_count = 0;

        // A record mapping "<hostname>.local." to IPv4 addresses
        if (service->address_ipv4.sin_family == AF_INET)
            additional[additional_count++] = service->record_a;

        // Add two test TXT records for our service instance name, will be coalesced into
        // one record with both key-value pair strings by the library
        additional[additional_count++] = service->txt_record[0];
        additional[additional_count++] = service->txt_record[1];
        additional[additional_count++] = service->txt_record[2];

        // Send the answer, unicast or multicast depending on flag in query
        uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
        mdns_string_t addrstr =
                ip_address_to_string(addrbuffer, sizeof(addrbuffer),
                                     (const struct sockaddr *) &service->record_aaaa.data.aaaa.addr,
                                     sizeof(service->record_aaaa.data.aaaa.addr));
        //printf("  --> answer %.*s IPv6 %.*s (%s)\n", MDNS_STRING_FORMAT(service->record_aaaa.name),
            //    MDNS_STRING_FORMAT(addrstr), (unicast ? "unicast" : "multicast"));

        if (unicast) {
            mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
                                      rtype, name.str, name.length, &answer, 0, 0, additional,
                                      additional_count);
        } else {
            mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), &answer, 0, 0,
                                        additional, additional_count);
        }
    }
    return 0;
}

// Open sockets for sending one-shot multicast queries from an ephemeral port
static int
open_client_sockets(int *sockets, int max_sockets, u16 port) {
    // When sending, each socket can only send to one network interface
    // Thus we need to open one socket for each interface and address family
    int num_sockets = 0;

#ifdef _WIN32
    ULONG address_size = 0;

    GetAdaptersAddresses(AF_INET, 0, NULL, NULL, &address_size);
    IP_ADAPTER_ADDRESSES* adapter_address = 0;
    unsigned int ret;

    adapter_address = (IP_ADAPTER_ADDRESSES*)malloc(address_size);
    ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST, 0,
                               adapter_address, &address_size);
    if (ret == ERROR_BUFFER_OVERFLOW) {
        free(adapter_address);
        adapter_address = 0;
    }

    if (!adapter_address || (ret != NO_ERROR)) {
        free(adapter_address);
        //printf("Failed to get network adapter addresses\n");
        return num_sockets;
    }

    int first_ipv4 = 1;
    int first_ipv6 = 1;
    for (PIP_ADAPTER_ADDRESSES adapter = adapter_address; adapter; adapter = adapter->Next) {
        //		if (adapter->TunnelType == TUNNEL_TYPE_TEREDO)
        //			continue;
        if (adapter->OperStatus != IfOperStatusUp)
            continue;

        for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; unicast;
             unicast = unicast->Next) {
            if (unicast->Address.lpSockaddr->sa_family == AF_INET) {
                struct sockaddr_in* saddr = (struct sockaddr_in*)unicast->Address.lpSockaddr;
                if ((saddr->sin_addr.S_un.S_un_b.s_b1 != 127) ||
                    (saddr->sin_addr.S_un.S_un_b.s_b2 != 0) ||
                    (saddr->sin_addr.S_un.S_un_b.s_b3 != 0) ||
                    (saddr->sin_addr.S_un.S_un_b.s_b4 != 1)) {
                    int log_addr = 0;
                    if (first_ipv4) {
                        service_address_ipv4 = *saddr;
                        // first_ipv4 = 0;
                        log_addr = 1;
                    }
                    has_ipv4 = 1;
                    if (num_sockets < max_sockets) {
                        saddr->sin_port = htons((unsigned short)port);
                        int sock = mdns_socket_open_ipv4(saddr);
                        if (sock >= 0) {
                            sockets[num_sockets++] = sock;
                            log_addr = 1;
                        } else {
                            log_addr = 0;
                        }
                    }
                    if (log_addr) {
                        char buffer[128];
                        mdns_string_t addr = ipv4_address_to_string(buffer, sizeof(buffer), saddr,
                                                                    sizeof(struct sockaddr_in));
                        //printf("Local IPv4 address: %.*s\n", MDNS_STRING_FORMAT(addr));
                    }
                }
            } else if (unicast->Address.lpSockaddr->sa_family == AF_INET6) {
                struct sockaddr_in6* saddr = (struct sockaddr_in6*)unicast->Address.lpSockaddr;
                static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0,
                                                          0, 0, 0, 0, 0, 0, 0, 1};
                static const unsigned char localhost_mapped[] = {0, 0, 0,    0,    0,    0, 0, 0,
                                                                 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
                if ((unicast->DadState == NldsPreferred) &&
                    memcmp(saddr->sin6_addr.s6_addr, localhost, 16) &&
                    memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) {
                    int log_addr = 0;
                    if (first_ipv6) {
                        service_address_ipv6 = *saddr;
                        first_ipv6 = 0;
                        log_addr = 1;
                    }
                    has_ipv6 = 1;
                    if (num_sockets < max_sockets) {
                        saddr->sin6_port = htons((unsigned short)port);
                        int sock = mdns_socket_open_ipv6(saddr);
                        if (sock >= 0) {
                            sockets[num_sockets++] = sock;
                            log_addr = 1;
                        } else {
                            log_addr = 0;
                        }
                    }
                    if (log_addr) {
                        char buffer[128];
                        mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr,
                                                                    sizeof(struct sockaddr_in6));
                        //printf("Local IPv6 address: %.*s\n", MDNS_STRING_FORMAT(addr));
                    }
                }
            }
        }
    }

    free(adapter_address);

#else

    struct ifaddrs *ifaddr = 0;

    if (getifaddrs(&ifaddr) < 0)
        printf("Unable to get interface addresses\n");

    int first_ipv4 = 1;
    int first_ipv6 = 1;
    for (struct ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr)
            continue;

        if (ifa->ifa_addr->sa_family == AF_INET) {
            struct sockaddr_in *saddr = (struct sockaddr_in *) ifa->ifa_addr;
            if (saddr->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
                int log_addr = 0;
                if (first_ipv4) {
                    service_address_ipv4 = *saddr;
//                    first_ipv4 = 0;
                    log_addr = 1;
                }
                has_ipv4 = 1;
                if (num_sockets < max_sockets) {
                    saddr->sin_port = htons(port);
                    int sock = mdns_socket_open_ipv4(saddr);
                    if (sock >= 0) {
                        sockets[num_sockets++] = sock;
                        log_addr = 1;
                    } else {
                        log_addr = 0;
                    }
                }
                if (log_addr) {
                    char buffer[128];
                    mdns_string_t addr = ipv4_address_to_string(buffer, sizeof(buffer), saddr,
                                                                sizeof(struct sockaddr_in));
                    //printf("Local IPv4 address: %.*s\n", MDNS_STRING_FORMAT(addr));
                }
            }
        }
#ifdef USE_IPV6
        else if (ifa->ifa_addr->sa_family == AF_INET6) {
            struct sockaddr_in6 *saddr = (struct sockaddr_in6 *) ifa->ifa_addr;
            static const unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0,
                                                      0, 0, 0, 0, 0, 0, 0, 1};
            static const unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0,
                                                             0, 0, 0xff, 0xff, 0x7f, 0, 0, 1};
            if (memcmp(saddr->sin6_addr.s6_addr, localhost, 16) &&
                memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) {
                int log_addr = 0;
                if (first_ipv6) {
                    service_address_ipv6 = *saddr;
                    first_ipv6 = 0;
                    log_addr = 1;
                }

                has_ipv6 = 1;
                if (num_sockets < max_sockets) {
                    saddr->sin6_port = htons(port);
                    int sock = mdns_socket_open_ipv6(saddr);
                    if (sock >= 0) {
                        sockets[num_sockets++] = sock;
                        log_addr = 1;
                    } else {
                        log_addr = 0;
                    }
                }
                if (log_addr) {
                    char buffer[128];
                    mdns_string_t addr = ipv6_address_to_string(buffer, sizeof(buffer), saddr,
                                                                sizeof(struct sockaddr_in6));
                    //printf("Local IPv6 address: %.*s\n", MDNS_STRING_FORMAT(addr));
                }
            }
        }
#endif
    }

    freeifaddrs(ifaddr);

#endif

    return num_sockets;
}

// Open sockets to listen to incoming mDNS queries on port 5353
static int
open_service_sockets(int *sockets, int max_sockets) {
    // When recieving, each socket can recieve data from all network interfaces
    // Thus we only need to open one socket for each address family
    int num_sockets = 0;

    // Call the client socket function to enumerate and get local addresses,
    // but not open the actual sockets
    open_client_sockets(0, 0, 0);

    if (num_sockets < max_sockets) {
        struct sockaddr_in sock_addr;
        memset(&sock_addr, 0, sizeof(struct sockaddr_in));
        sock_addr.sin_family = AF_INET;
#ifdef _WIN32
        sock_addr.sin_addr.s_addr = INADDR_ANY;
#else
        sock_addr.sin_addr.s_addr = INADDR_ANY;
#endif
        sock_addr.sin_port = htons(MDNS_PORT);
#ifdef __APPLE__
        sock_addr.sin_len = sizeof(struct sockaddr_in);
#endif
        int sock = mdns_socket_open_ipv4(&sock_addr);
        if (sock >= 0)
            sockets[num_sockets++] = sock;
    }
#ifdef USE_IPV6
    if (num_sockets < max_sockets) {
        struct sockaddr_in6 sock_addr;
        memset(&sock_addr, 0, sizeof(struct sockaddr_in6));
        sock_addr.sin6_family = AF_INET6;
        sock_addr.sin6_addr = in6addr_any;
        sock_addr.sin6_port = htons(MDNS_PORT);
#ifdef __APPLE__
        sock_addr.sin6_len = sizeof(struct sockaddr_in6);
#endif
        int sock = mdns_socket_open_ipv6(&sock_addr);
        if (sock >= 0)
            sockets[num_sockets++] = sock;
    }
#endif
    return num_sockets;
}

// Send a DNS-SD query
static int
send_dns_sd(void) {
    int sockets[32];
    int num_sockets;
    num_sockets = open_client_sockets(sockets, sizeof(sockets) / sizeof(sockets[0]), 0);
    if (num_sockets <= 0) {
        //printf("Failed to open any client sockets\n");
        return -1;
    }
    //printf("Opened %d socket%s for DNS-SD\n", num_sockets, num_sockets ? "s" : "");

    //printf("Sending DNS-SD discovery\n");
    for (int isock = 0; isock < num_sockets; ++isock) {
        if (mdns_discovery_send(sockets[isock]))
            printf("Failed to send DNS-DS discovery: %s\n", strerror(errno));
    }

    size_t capacity = 2048;
    void *buffer = malloc(capacity);
    void *user_data = 0;
    size_t records;

    // This is a simple implementation that loops for 5 seconds or as long as we get replies
    int res;
    //printf("Reading DNS-SD replies\n");
    do {
        struct timeval timeout;
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;

        int nfds = 0;
        fd_set readfs;
        FD_ZERO(&readfs);
        for (int isock = 0; isock < num_sockets; ++isock) {
            if (sockets[isock] >= nfds)
                nfds = sockets[isock] + 1;
            FD_SET(sockets[isock], &readfs);
        }

        records = 0;
        res = select(nfds, &readfs, 0, 0, &timeout);
        if (res > 0) {
            for (int isock = 0; isock < num_sockets; ++isock) {
                if (FD_ISSET(sockets[isock], &readfs)) {
                    records += mdns_discovery_recv(sockets[isock], buffer, capacity,
                                                   (mdns_record_callback_fn) &query_callback, user_data);
                }
            }
        }
    } while (res > 0);

    free(buffer);

    for (int isock = 0; isock < num_sockets; ++isock)
        mdns_socket_close(sockets[isock]);
    //printf("Closed socket%s\n", num_sockets ? "s" : "");

    return 0;
}

// Send a mDNS query
static int
send_mdns_query(const char *service, int record) {
    int sockets[32];
    int query_id[32];
    int num_sockets = open_client_sockets(sockets, sizeof(sockets) / sizeof(sockets[0]), 0);
    if (num_sockets <= 0) {
        //printf("Failed to open any client sockets\n");
        return -1;
    }
    //printf("Opened %d socket%s for mDNS query\n", num_sockets, num_sockets ? "s" : "");

    size_t capacity = 2048;
    void *buffer = malloc(capacity);
    void *user_data = 0;
    size_t records;

    const char *record_name = "PTR";
    if (record == MDNS_RECORDTYPE_SRV)
        record_name = "SRV";
    else if (record == MDNS_RECORDTYPE_A)
        record_name = "A";
    else if (record == MDNS_RECORDTYPE_AAAA)
        record_name = "AAAA";
    else
        record = MDNS_RECORDTYPE_PTR;

    //printf("Sending mDNS query: %s %s\n", service, record_name);
    for (int isock = 0; isock < num_sockets; ++isock) {
        query_id[isock] =
                mdns_query_send(sockets[isock], record, service, strlen(service), buffer, capacity, 0);
        // if (query_id[isock] < 0)
            //printf("Failed to send mDNS query: %s\n", strerror(errno));
    }

    // This is a simple implementation that loops for 5 seconds or as long as we get replies
    int res;
    //printf("Reading mDNS query replies\n");
    do {
        struct timeval timeout;
        timeout.tv_sec = 10;
        timeout.tv_usec = 0;

        int nfds = 0;
        fd_set readfs;
        FD_ZERO(&readfs);
        for (int isock = 0; isock < num_sockets; ++isock) {
            if (sockets[isock] >= nfds)
                nfds = sockets[isock] + 1;
            FD_SET(sockets[isock], &readfs);
        }

        records = 0;
        res = select(nfds, &readfs, 0, 0, &timeout);
        if (res > 0) {
            for (int isock = 0; isock < num_sockets; ++isock) {
                if (FD_ISSET(sockets[isock], &readfs)) {
                    records += mdns_query_recv(sockets[isock], buffer, capacity,
                                               (mdns_record_callback_fn) &query_callback,
                                               user_data, query_id[isock]);
                }
                FD_SET(sockets[isock], &readfs);
            }
        }
    } while (res > 0);

    free(buffer);

    for (int isock = 0; isock < num_sockets; ++isock)
        mdns_socket_close(sockets[isock]);
    //printf("Closed socket%s\n", num_sockets ? "s" : "");

    return 0;
}

service_t service = {0};
char qualified_hostname_buffer[256] = {0};
char service_instance_buffer[256] = {0};
char service_port_buffer[8] = {0};

int
make_service(const char *hostname, const char *service_name, u16 service_port) {
    mdns_string_t service_string = (mdns_string_t) {service_name, strlen(service_name)};
    mdns_string_t hostname_string = (mdns_string_t) {hostname, strlen(hostname)};

    // Build the service instance "<hostname>.<_service-name>._tcp.local." string
    snprintf(service_instance_buffer, sizeof(service_instance_buffer) - 1, "%.*s.%.*s._udp.local.",
             MDNS_STRING_FORMAT(hostname_string), MDNS_STRING_FORMAT(service_string));
    mdns_string_t service_instance_string =
            (mdns_string_t) {service_instance_buffer, strlen(service_instance_buffer)};

    // Build the "<hostname>.local." string
    snprintf(qualified_hostname_buffer, sizeof(qualified_hostname_buffer) - 1, "%.*s._pac._udp.local.",
             MDNS_STRING_FORMAT(hostname_string));
    mdns_string_t hostname_qualified_string =
            (mdns_string_t) {qualified_hostname_buffer, strlen(qualified_hostname_buffer)};

    service.service = service_string;
    service.hostname = hostname_string;
    service.service_instance = service_instance_string;
    service.hostname_qualified = hostname_qualified_string;
    service.port = service_port;

    // Setup our mDNS records

    // PTR record reverse mapping "<_service-name>._tcp.local." to
    // "<hostname>.<_service-name>._tcp.local."
    service.record_ptr = (mdns_record_t) {.name = service.service,
            .type = MDNS_RECORDTYPE_PTR,
            .data.ptr.name = service.service_instance};

    // SRV record mapping "<hostname>.<_service-name>._tcp.local." to
    // "<hostname>.local." with port. Set weight & priority to 0.
    service.record_srv = (mdns_record_t) {.name = service.service_instance,
            .type = MDNS_RECORDTYPE_SRV,
            .data.srv.name = service.hostname_qualified,
            .data.srv.port = service.port,
            .data.srv.priority = 0,
            .data.srv.weight = 0};

    service.record_a.name = service.hostname_qualified;

    service.record_aaaa.name = service.hostname_qualified;

    // Add two test TXT records for our service instance name, will be coalesced into
    // one record with both key-value pair strings by the library
    service.txt_record[0] = (mdns_record_t) {.name = service.service_instance,
            .type = MDNS_RECORDTYPE_TXT,
            .data.txt.key = {MDNS_STRING_CONST("protocol")},
            .data.txt.value = {MDNS_STRING_CONST("FIRMATA")}};
    service.txt_record[1] = (mdns_record_t) {.name = service.service_instance,
            .type = MDNS_RECORDTYPE_TXT,
            .data.txt.key = {MDNS_STRING_CONST("model")},
            .data.txt.value = {MDNS_STRING_CONST(HWMODEL)}};
    snprintf(service_port_buffer, sizeof(service_port_buffer) - 1, "%d", service_port);
    service.txt_record[2] = (mdns_record_t) {.name = service.service_instance,
            .type = MDNS_RECORDTYPE_TXT,
            .data.txt.key = {MDNS_STRING_CONST("port")},
            .data.txt.value = {service_port_buffer, strlen(service_port_buffer)}};
    //printf("Service mDNS: %s:%d\n", service_string.str, service_port);
    //printf("Hostname: %s\n", hostname_string.str);
    //printf("service_instance: %s\n", service_instance_string.str);
    //printf("hostname_qualified: %s\n", hostname_qualified_string.str);
    return 0;
}

// Provide a mDNS service, answering incoming DNS-SD and mDNS queries
int
service_mdns() {
    int sockets[32];
    int num_sockets = open_service_sockets(sockets, sizeof(sockets) / sizeof(sockets[0]));
    if (num_sockets <= 0) {
        //printf("Failed to open any service sockets\n");
        return -1;
    }
    service.address_ipv4 = service_address_ipv4;
    service.address_ipv6 = service_address_ipv6;
    // A/AAAA records mapping "<hostname>.local." to IPv4/IPv6 addresses
    service.record_a = (mdns_record_t) {.name = service.hostname_qualified,
            .type = MDNS_RECORDTYPE_A,
            .data.a.addr = service.address_ipv4};
#ifdef USE_IPV6
    service.record_aaaa = (mdns_record_t) {.name = service.hostname_qualified,
            .type = MDNS_RECORDTYPE_AAAA,
            .data.aaaa.addr = service.address_ipv6};
#endif
    //printf("Opened %d socket%s for mDNS service\n", num_sockets, num_sockets ? "s" : "");

    size_t capacity = 2048;
    void *buffer = malloc(capacity);

    // Send an announcement on startup of service
    {
        mdns_record_t additional[5] = {0};
        size_t additional_count = 0;
        additional[additional_count++] = service.record_srv;
        if (service.address_ipv4.sin_family == AF_INET)
            additional[additional_count++] = service.record_a;
#ifdef USE_IPV6
        if (service.address_ipv6.sin6_family == AF_INET6)
            additional[additional_count++] = service.record_aaaa;
#endif
        additional[additional_count++] = service.txt_record[0];
        additional[additional_count++] = service.txt_record[1];
        additional[additional_count++] = service.txt_record[2];

        for (int isock = 0; isock < num_sockets; ++isock)
            mdns_announce_multicast(sockets[isock], buffer, capacity, &service.record_ptr, 0, 0,
                                    additional, additional_count);
    }

    // This is a crude implementation that checks for incoming queries
    while (1) {
        int nfds = 0;
        fd_set readfs;
        FD_ZERO(&readfs);
        for (int isock = 0; isock < num_sockets; ++isock) {
            if (sockets[isock] >= nfds)
                nfds = sockets[isock] + 1;
            FD_SET(sockets[isock], &readfs);
        }

        if (select(nfds, &readfs, 0, 0, 0) >= 0) {
            for (int isock = 0; isock < num_sockets; ++isock) {
                if (FD_ISSET(sockets[isock], &readfs)) {
                    mdns_socket_listen(sockets[isock], buffer, capacity, (mdns_record_callback_fn) &service_callback,
                                       &service);
                }
                FD_SET(sockets[isock], &readfs);
            }
        } else {
            break;
        }
    }

    free(buffer);

    for (int isock = 0; isock < num_sockets; ++isock)
        mdns_socket_close(sockets[isock]);
    //printf("Closed socket%s\n", num_sockets ? "s" : "");

    return 0;
}

#ifdef MDNS_FUZZING

#undef printf

// Fuzzing by piping random data into the recieve functions
static void
fuzz_mdns(void) {
#define MAX_FUZZ_SIZE 4096
#define MAX_PASSES (1024 * 1024 * 1024)

    static uint8_t fuzz_mdns_services_query[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, '_',
        's',  'e',  'r',  'v',  'i',  'c',  'e',  's',  0x07, '_',  'd',  'n',  's',  '-',
        's',  'd',  0x04, '_',  'u',  'd',  'p',  0x05, 'l',  'o',  'c',  'a',  'l',  0x00};

    uint8_t* buffer = malloc(MAX_FUZZ_SIZE);
    uint8_t* strbuffer = malloc(MAX_FUZZ_SIZE);
    for (int ipass = 0; ipass < MAX_PASSES; ++ipass) {
        size_t size = rand() % MAX_FUZZ_SIZE;
        for (size_t i = 0; i < size; ++i)
            buffer[i] = rand() & 0xFF;

        if (ipass % 4) {
            // Crafted fuzzing, make sure header is reasonable
            memcpy(buffer, fuzz_mdns_services_query, sizeof(fuzz_mdns_services_query));
            uint16_t* header = (uint16_t*)buffer;
            header[0] = 0;
            header[1] = htons(0x8400);
            for (int ival = 2; ival < 6; ++ival)
                header[ival] = rand() & 0xFF;
        }
        mdns_discovery_recv(0, (void*)buffer, size, query_callback, 0);

        mdns_socket_listen(0, (void*)buffer, size, service_callback, 0);

        if (ipass % 4) {
            // Crafted fuzzing, make sure header is reasonable (1 question claimed).
            // Earlier passes will have done completely random data
            uint16_t* header = (uint16_t*)buffer;
            header[2] = htons(1);
        }
        mdns_query_recv(0, (void*)buffer, size, query_callback, 0, 0);

        // Fuzzing by piping random data into the parse functions
        size_t offset = size ? (rand() % size) : 0;
        size_t length = size ? (rand() % (size - offset)) : 0;
        mdns_record_parse_ptr(buffer, size, offset, length, strbuffer, MAX_FUZZ_SIZE);

        offset = size ? (rand() % size) : 0;
        length = size ? (rand() % (size - offset)) : 0;
        mdns_record_parse_srv(buffer, size, offset, length, strbuffer, MAX_FUZZ_SIZE);

        struct sockaddr_in addr_ipv4;
        offset = size ? (rand() % size) : 0;
        length = size ? (rand() % (size - offset)) : 0;
        mdns_record_parse_a(buffer, size, offset, length, &addr_ipv4);

        struct sockaddr_in6 addr_ipv6;
        offset = size ? (rand() % size) : 0;
        length = size ? (rand() % (size - offset)) : 0;
        mdns_record_parse_aaaa(buffer, size, offset, length, &addr_ipv6);

        offset = size ? (rand() % size) : 0;
        length = size ? (rand() % (size - offset)) : 0;
        mdns_record_parse_txt(buffer, size, offset, length, (mdns_record_txt_t*)strbuffer,
                              MAX_FUZZ_SIZE);

        if (ipass && !(ipass % 10000))
            //printf("Completed fuzzing pass %d\n", ipass);
    }

    free(buffer);
    free(strbuffer);
}

#endif
#if 0
int
main(int argc, const char* const* argv) {
    int mode = 0;
    const char* service = "_test-mdns._tcp.local.";
    const char* hostname = "dummy-host";
    int query_record = MDNS_RECORDTYPE_PTR;
    int service_port = 42424;

#ifdef _WIN32

    WORD versionWanted = MAKEWORD(2, 2);
    WSADATA wsaData;
    if (WSAStartup(versionWanted, &wsaData)) {
        //printf("Failed to initialize WinSock\n");
        return -1;
    }

    char hostname_buffer[256];
    DWORD hostname_size = (DWORD)sizeof(hostname_buffer);
    if (GetComputerNameA(hostname_buffer, &hostname_size))
        hostname = hostname_buffer;

#else

    char hostname_buffer[256];
    size_t hostname_size = sizeof(hostname_buffer);
    if (gethostname(hostname_buffer, hostname_size) == 0)
        hostname = hostname_buffer;

#endif

    for (int iarg = 0; iarg < argc; ++iarg) {
        if (strcmp(argv[iarg], "--discovery") == 0) {
            mode = 0;
        } else if (strcmp(argv[iarg], "--query") == 0) {
            mode = 1;
            ++iarg;
            if (iarg < argc)
                service = argv[iarg++];
            if (iarg < argc) {
                const char* record_name = service;
                service = argv[iarg++];
                if (strcmp(record_name, "PTR") == 0)
                    query_record = MDNS_RECORDTYPE_PTR;
                else if (strcmp(record_name, "SRV") == 0)
                    query_record = MDNS_RECORDTYPE_SRV;
                else if (strcmp(record_name, "A") == 0)
                    query_record = MDNS_RECORDTYPE_A;
                else if (strcmp(record_name, "AAAA") == 0)
                    query_record = MDNS_RECORDTYPE_AAAA;
            }
        } else if (strcmp(argv[iarg], "--service") == 0) {
            mode = 2;
            ++iarg;
            if (iarg < argc)
                service = argv[iarg];
        } else if (strcmp(argv[iarg], "--hostname") == 0) {
            ++iarg;
            if (iarg < argc)
                hostname = argv[iarg];
        } else if (strcmp(argv[iarg], "--port") == 0) {
            ++iarg;
            if (iarg < argc)
                service_port = atoi(argv[iarg]);
        }
    }

#ifdef MDNS_FUZZING
    fuzz_mdns();
#else
    int ret;
    if (mode == 0)
        ret = send_dns_sd();
    else if (mode == 1)
        ret = send_mdns_query(service, query_record);
    else if (mode == 2)
        ret = service_mdns(hostname, service, service_port);
#endif

#ifdef _WIN32
    WSACleanup();
#endif

    return 0;
}
#endif
